package com.yxsk.relay.job.admin.web.controller;

import com.yxsk.relay.job.admin.data.entity.BaseEntity;
import com.yxsk.relay.job.admin.data.entity.JobDetails;
import com.yxsk.relay.job.admin.data.entity.JobExecuteLog;
import com.yxsk.relay.job.admin.data.entity.JobTriggerLog;
import com.yxsk.relay.job.admin.data.service.JobDetailService;
import com.yxsk.relay.job.admin.data.service.JobExecuteLogService;
import com.yxsk.relay.job.admin.data.service.JobTriggerLogService;
import com.yxsk.relay.job.admin.web.common.exception.RelayAdminWebRuntimeException;
import com.yxsk.relay.job.admin.web.controller.dto.ExecuteLogOutlineDto;
import com.yxsk.relay.job.admin.web.controller.dto.ExecuteLogOutlineRequestDto;
import com.yxsk.relay.job.component.common.protocol.message.base.ResultResponse;
import com.yxsk.relay.job.component.common.utils.DateUtils;
import com.yxsk.relay.job.component.common.vo.ExecuteResult;
import com.yxsk.relay.job.component.common.vo.TriggerResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.Valid;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;

/**
 * @Author 11376
 * @CreaTime 2019/7/2 17:41
 * @Description
 */
@Controller
@RequestMapping("/admin/quartz/job/dashboard")
public class DashboardController {

    @Autowired
    private JobTriggerLogService triggerLogService;

    @Autowired
    private JobExecuteLogService executeLogService;

    @Autowired
    private JobDetailService jobDetailService;

    /**
     * @param requestDto
     * @Author 11376
     * @Description 任务执行情况
     * @CreateTime 2019/7/2 17:49
     * @Return
     */
    @PostMapping("/count")
    @ResponseBody
    public ResultResponse<ExecuteLogOutlineDto> execute(@Valid ExecuteLogOutlineRequestDto requestDto) {
        // 默认查询触发时间为24以内的数据
        Date startTime = getDefaultTime(requestDto.getStartTime(), DateUtils.getCurrentDate(), 24);
        Date endTime = getDefaultTime(requestDto.getEndTime(), DateUtils.getCurrentDate(), 0);

        List<String> jobIds = new ArrayList<>();
        // app名称
        if (StringUtils.hasLength(requestDto.getAppId())) {
            List<JobDetails> jobDetails = this.jobDetailService.findByAppId(requestDto.getAppId());
            if (!CollectionUtils.isEmpty(jobDetails)) {
                jobDetails.stream().forEach(details -> jobIds.add(details.getId()));
            }
        }

        // JobId
        if (StringUtils.hasLength(requestDto.getJobId())) {
            // 清空 jobIds
            jobIds.clear();
            JobDetails details = this.jobDetailService.findById(requestDto.getJobId());
            if (details == null) {
                return ResultResponse.fail("ADMIN-QUERY-0001", "未找到任务信息");
            }
            jobIds.add(details.getId());
        }

        ExecuteLogOutlineDto outlineDto;
        if (ExecuteLogOutlineRequestDto.DataModel.TRIGGER.getModel().equals(requestDto.getModel())) {
            // 查询任务触发数据
            List<JobTriggerLog> triggerLogs = this.triggerLogService.findData(startTime, endTime, CollectionUtils.isEmpty(jobIds) ? null : jobIds);
            outlineDto = this.getTriggerLogDataDto(triggerLogs, startTime, endTime, requestDto.getGranularity());
        } else if (ExecuteLogOutlineRequestDto.DataModel.EXECUTE.getModel().equals(requestDto.getModel())) {
            // 查询任务执行数据
            List<JobExecuteLog> executeLogs = this.executeLogService.findData(startTime, endTime, CollectionUtils.isEmpty(jobIds) ? null : jobIds);
            outlineDto = this.getExecuteLogDataDto(executeLogs, startTime, endTime, requestDto.getGranularity());
        } else {
            return ResultResponse.fail("ADMIN-QUERY-0001", "不支持的数据模型");
        }

        // 合并重复
        mergeRepeat(outlineDto);

        return ResultResponse.ok(outlineDto);
    }

    private void mergeRepeat(ExecuteLogOutlineDto outlineDto) {
        List<String> time = outlineDto.getTime();
        // total
        Map<String, Integer> total = new TreeMap<>();
        // failed
        Map<String, Integer> failed = new TreeMap<>();

        for (int i = 0; i < time.size(); i++) {
            String key = time.get(i);
            Integer totalCount = total.get(key);
            totalCount = totalCount == null ? outlineDto.getTotalTimes().get(i) : outlineDto.getTotalTimes().get(i) + totalCount;
            total.put(key, totalCount);

            Integer failedCount = failed.get(key);
            failedCount = failedCount == null ? outlineDto.getFailedTimes().get(i) : outlineDto.getFailedTimes().get(i) + failedCount;
            failed.put(key, failedCount);
        }
        outlineDto.setTime(new LinkedList<>(total.keySet()));
        outlineDto.setTotalTimes(new LinkedList<>(total.values()));
        outlineDto.setFailedTimes(new LinkedList<>(failed.values()));
    }

    private ExecuteLogOutlineDto getExecuteLogDataDto(List<JobExecuteLog> executeLogs, Date startTime, Date endTime, Integer granularity) {
        if (CollectionUtils.isEmpty(executeLogs)) {
            executeLogs = new LinkedList<>();
        }

        // 先以时间排序
        Collections.sort(executeLogs, (o1, o2) -> DateUtils.compare(o1.getBeginTime(), o2.getBeginTime()));

        return calculateCount(executeLogs, startTime, endTime, granularity);
    }

    private ExecuteLogOutlineDto calculateCount(List<? extends BaseEntity> logs, Date startDte, Date endDte, Integer granularity) {
        // 计算时间差, 单位：秒
        long diff = DateUtils.diffSeconds(startDte, endDte);
        // 均分时间差
        Long timeGranularity = new BigDecimal(diff).divide(new BigDecimal(granularity), 0, BigDecimal.ROUND_HALF_UP).longValue();

        String pattern;
        if (timeGranularity <= 60L) {
            // 以分钟计
            pattern = "yyyy-MM-dd HH:mm";
        } else if (timeGranularity > 60 && timeGranularity <= 60 * 60) {
            // 以小时计
            pattern = "yyyy-MM-dd HH:mm";
        } else {
            // 以天计
            pattern = "yyyy-MM-dd";
        }

        ExecuteLogOutlineDto outlineDto = new ExecuteLogOutlineDto();
        List<String> time = new LinkedList<>();
        List<Integer> totalTimes = new LinkedList<>();
        List<Integer> failedTimes = new LinkedList<>();

        // 记 endDate 游标
        endDte = DateUtils.addSeconds(startDte, timeGranularity);
        for (int i = 0; i < granularity; i++) {
            // 时间点取中间时间
            time.add(DateUtils.formatDate(DateUtils.addSeconds(startDte, timeGranularity / 2), pattern));
            // 统计时间段总次数
            Integer count = count(logs, startDte, endDte, false);
            totalTimes.add(count);

            // 统计时间段失败次数
            Integer failedCount = count(logs, startDte, endDte, true);
            failedTimes.add(failedCount);

            // 时间游标后移
            startDte = DateUtils.addSeconds(startDte, timeGranularity);
            endDte = DateUtils.addSeconds(endDte, timeGranularity);
        }
        outlineDto.setTime(time);
        outlineDto.setTotalTimes(totalTimes);
        outlineDto.setFailedTimes(failedTimes);
        return outlineDto;
    }

    private ExecuteLogOutlineDto getTriggerLogDataDto(List<JobTriggerLog> triggerLogs, Date startTime, Date endTime, Integer granularity) {
        if (CollectionUtils.isEmpty(triggerLogs)) {
            triggerLogs = new LinkedList<>();
        }

        // 先以时间排序
        Collections.sort(triggerLogs, (o1, o2) -> DateUtils.compare(o1.getTriggerTime(), o2.getTriggerTime()));

        return calculateCount(triggerLogs, startTime, endTime, granularity);
    }

    private Integer count(List<? extends BaseEntity> logs, Date startDte, Date endDte, boolean isFailed) {
        int count = 0;
        if (!CollectionUtils.isEmpty(logs)) {
            for (BaseEntity entity : logs) {
                if (entity instanceof JobTriggerLog) {
                    JobTriggerLog log = (JobTriggerLog) entity;
                    if (DateUtils.compare(log.getTriggerTime(), startDte) >= 0 && DateUtils.compare(log.getTriggerTime(), endDte) < 0) {
                        if (isFailed) {
                            if (!TriggerResult.TriggerResultCode.OK.getCode().equals(log.getTriggerResultCode())) {
                                // 计失败数
                                ++count;
                            }
                        } else {
                            // 计总数
                            ++count;
                        }
                    }
                    if (DateUtils.compare(log.getTriggerTime(), endDte) >= 0) {
                        // 计数
                        break;
                    }
                } else if (entity instanceof JobExecuteLog) {
                    JobExecuteLog log = (JobExecuteLog) entity;
                    if (DateUtils.compare(log.getBeginTime(), startDte) >= 0 && DateUtils.compare(log.getBeginTime(), endDte) < 0) {
                        if (isFailed) {
                            // 任务执行成功或执行中不计失败
                            if (!ExecuteResult.ExecuteResultCode.OK.getCode().equals(log.getExecuteStatus()) && !ExecuteResult.ExecuteResultCode.EXECUTING.getCode().equals(log.getExecuteStatus())) {
                                // 计失败数
                                ++count;
                            }
                        } else {
                            // 计总数
                            ++count;
                        }
                    }
                    if (DateUtils.compare(log.getBeginTime(), endDte) >= 0) {
                        // 计数
                        break;
                    }
                }
            }
        }
        return count;
    }

    private Date getDefaultTime(String time, Date defaultDate, Integer laterHours) {
        if (StringUtils.hasLength(time)) {
            try {
                return DateUtils.parseDate(time, "yyyy-MM-dd HH:mm:ss");
            } catch (ParseException e) {
                throw new RelayAdminWebRuntimeException(e);
            }
        }
        if (laterHours != null) {
            if (laterHours > 0) {
                defaultDate = DateUtils.subtractHours(defaultDate, laterHours);
            } else {
                defaultDate = DateUtils.addHours(defaultDate, 0 - laterHours);
            }
        }
        return defaultDate;
    }

}
